﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Audio;
using XnaAux;

namespace PrisonStep
{
    public class AnimatedModel
    {

        #region Member Variables

        /// <summary>
        /// Reference to the game that uses this class
        /// </summary>
        /// 
        private PrisonGame mGame;

        /// <summary>
        /// Name of the asset we are going to load
        /// </summary>
        private string mAsset;

        /// <summary>
        /// The loaded model
        /// </summary>
        private Model mModel;

        private Matrix[] mBindTransforms;
        private Matrix[] mBoneTransforms;
        private Matrix[] mAbsoTransforms;

        private AnimationClips.Clip mClip = null;

        private AnimationPlayer mPlayer = null;

        private Matrix[] mSkinTransforms = null;

        private List<int> mSkelToBone = null;

        private Matrix[] mInverseBindTransform = null;

        /// <summary>
        /// The number of skinning matrices in SKinnedEffect.fx. This must
        /// match the number in SkinnedEffect.fx.
        /// </summary>
        public const int mNumSkinBones = 57;

        private Matrix mRootMatrixRaw = Matrix.Identity;

        private Matrix mDeltaMatrix = Matrix.Identity;

        public Vector3 mDeltaPosition;

        /// <summary>
        /// What region the model is currently located in.
        /// </summary>
        private string mRegion;
        
        int mLastSection;

        int mAdjacent;
        #endregion

        #region Properties
        public Matrix DeltaMatrix { get { return mDeltaMatrix; } }

        public Matrix RootMatrix { get { return mInverseBindTransform[mSkelToBone[0]] * mRootMatrixRaw; } }

        public Model model { get { return mModel; } }

        public bool Skinned { get; set; }

        public float SlimeWeight { get; set; }

        public AnimationClips.Clip Clip { get { return mClip; } }

        public string Region { get { return mRegion; } set { mRegion = value; } }

        public int Adjacent { get { return mAdjacent; } set { mAdjacent = value; mLastSection = mAdjacent; } }
        public int Last { get { return mLastSection; } set { mLastSection = value; } }
        #endregion

        #region Constructor
        /// <summary>
        /// This class describes a single animation clip we load from
        /// an asset
        /// </summary>
        private class AssetClip
        {
            public AssetClip(string aName, string aAsset)
            {
                Name = aName;
                Asset = aAsset;
                TheClip = null;
            }
            public string Name { get; set; }
            public string Asset { get; set; }
            public AnimationClips.Clip TheClip { get; set; }
        }

        private Dictionary<string, AssetClip> mAssetClips = new Dictionary<string, AssetClip>();

        #endregion

        public AnimatedModel(PrisonGame aGame, string aAsset)
        {
            this.mGame = aGame;
            this.mAsset = aAsset;

            mSkinTransforms = new Matrix[57];
            for (int i = 0 ; i < mSkinTransforms.Length; i++)
            {
                mSkinTransforms[i] = Matrix.Identity;
            }
            
        }

        public void LoadContent(ContentManager aContent)
        {
            mModel = aContent.Load<Model>(mAsset);

            int lBoneCnt = mModel.Bones.Count;
            
            mBindTransforms = new Matrix[lBoneCnt];
            mBoneTransforms = new Matrix[lBoneCnt];
            mAbsoTransforms = new Matrix[lBoneCnt];

            mModel.CopyBoneTransformsTo(mBindTransforms);
            mModel.CopyBoneTransformsTo(mBoneTransforms);
            mModel.CopyAbsoluteBoneTransformsTo(mAbsoTransforms);

            AnimationClips lClips = mModel.Tag as AnimationClips;
            if (lClips != null && lClips.SkelToBone.Count > 0)
            {
                mSkelToBone = lClips.SkelToBone;

                mInverseBindTransform = new Matrix[lBoneCnt];
                mSkinTransforms = new Matrix[mNumSkinBones];

                mModel.CopyAbsoluteBoneTransformsTo(mInverseBindTransform);

                for (int b = 0; b < mInverseBindTransform.Length; b++)
                    mInverseBindTransform[b] = Matrix.Invert(mInverseBindTransform[b]);

                for (int i = 0; i < mSkinTransforms.Length; i++)
                    mSkinTransforms[i] = Matrix.Identity;
            }

            foreach (AssetClip lClip in mAssetClips.Values)
            {
                Model lClipModel = aContent.Load<Model>(lClip.Asset);
                AnimationClips lModelClips = lClipModel.Tag as AnimationClips;
                lClip.TheClip = lModelClips.Clips["Take 001"];
            }

            //PlayClip("Take 001");
        }

        /// <summary>
        /// This function is called to update this component
        /// to the current game time
        /// </summary>
        /// <param name="aGameTime"></param>
        public void Update(double aDelta)
        {

            //mAngle += (float)(Math.PI / 2 * lDelta);

            if (mClip != null)
            {
                // Update the clip
                mPlayer.Update(aDelta);
                for (int b = 0; b < mClip.BoneCount; b++)
                {
                    AnimationClips.Bone lBone = mPlayer.GetBone(b);
                    if (!lBone.Valid)
                        continue;

                    Vector3 lScale = new Vector3(mBindTransforms[b].Right.Length(),
                        mBindTransforms[b].Up.Length(),
                        mBindTransforms[b].Backward.Length());

                    mBoneTransforms[b] = Matrix.CreateScale(lScale) *
                        Matrix.CreateFromQuaternion(lBone.Rotation) *
                        Matrix.CreateTranslation(lBone.Translation);
                }

                mModel.CopyBoneTransformsFrom(mBoneTransforms);
            }
            if (mSkelToBone != null)
            {
                int lRootBone = mSkelToBone[0];

                mDeltaMatrix = Matrix.Invert(mRootMatrixRaw) * mBoneTransforms[lRootBone];
                mDeltaPosition = mBoneTransforms[lRootBone].Translation - mRootMatrixRaw.Translation;

                mRootMatrixRaw = mBoneTransforms[lRootBone];

                mBoneTransforms[lRootBone] = mBindTransforms[lRootBone];
            }


            mModel.CopyBoneTransformsFrom(mBoneTransforms);
            mModel.CopyAbsoluteBoneTransformsTo(mAbsoTransforms);
        }

        /// <summary>
        /// This function is called to draw this game component
        /// </summary>
        /// <param name="aGraphics">Devicet to draw the model on.</param>
        /// <param name="aGameTime">Currentgame time.</param>
        /// <param name="aTransform">Transform that puts the model where we want it.</param>
        public void Draw(GraphicsDeviceManager aGraphics, GameTime aGameTime, Matrix aTransform)
        {
            DrawModel(aGraphics, mModel, aTransform);
        }

        public void DrawModel(GraphicsDeviceManager aGraphics, Model aModel, Matrix aWorld)
        {
            if (mSkelToBone != null)
            {
                for (int b = 0; b < mSkelToBone.Count; b++)
                {
                    int n = mSkelToBone[b];
                    mSkinTransforms[b] = mInverseBindTransform[n] * mAbsoTransforms[n];
                }
            }
            foreach (ModelMesh lMesh in aModel.Meshes)
            {
                foreach (Effect lEffect in lMesh.Effects)
                {

                    if (lEffect is BasicEffect)
                    {
                        BasicEffect lBasicEffect = lEffect as BasicEffect;
                        lBasicEffect.EnableDefaultLighting();
                        lBasicEffect.World = mAbsoTransforms[lMesh.ParentBone.Index] * aWorld;
                        lBasicEffect.View = mGame.Camera.View;
                        lBasicEffect.Projection = mGame.Camera.Projection;

                    }
                    else
                    {
                        lEffect.Parameters["World"].SetValue(mAbsoTransforms[lMesh.ParentBone.Index] * aWorld);
                        lEffect.Parameters["View"].SetValue(mGame.Camera.View);
                        lEffect.Parameters["Projection"].SetValue(mGame.Camera.Projection);
                        if (Skinned)
                            lEffect.Parameters["Bones"].SetValue(mSkinTransforms);
                        lEffect.Parameters["Weight"].SetValue(SlimeWeight);
                        
                        int lSection = mGame.GetRegion(mRegion);
                        // Tests to see which was the last main section we were in.
                        if (lSection > 0)
                        {
                            //mAdjacent = mLastSection;
                            mLastSection = lSection; 
                        }
                        int lDoorRegion = mGame.GetDoorRegion(mRegion);
                        int lDoor = mGame.TestAdjacentRegion(mLastSection, lDoorRegion);
                        mGame.UniversalDoor = mGame.GetDoorRegion(mRegion);
                        if (lDoor > 0)
                            mAdjacent = lDoor;
                        //else
                        //    mAdjacent = 0;
                        float lWeight = mGame.TestForOpenDoors(lDoorRegion);
                        if ( lWeight < 0)
                            lWeight = 0;


                        lEffect.Parameters["Light1Location"].SetValue(mGame.LightInfo(mLastSection,0));
                        lEffect.Parameters["Light1Color"].SetValue(mGame.LightInfo(mLastSection, 1));
                        lEffect.Parameters["Light2Location"].SetValue(mGame.LightInfo(mLastSection,2));
                        lEffect.Parameters["Light2Color"].SetValue(mGame.LightInfo(mLastSection, 3));
                        lEffect.Parameters["Light3Location"].SetValue(mGame.LightInfo(mLastSection,4));
                        lEffect.Parameters["Light3Color"].SetValue(mGame.LightInfo(mLastSection, 5));
                        lEffect.Parameters["Light4Location"].SetValue(mGame.LightInfo(mAdjacent,0));
                        lEffect.Parameters["Light4Color"].SetValue(mGame.LightInfo(mAdjacent, 1) * lWeight);
                    }
                }
                lMesh.Draw();
            }
        }

        /// <summary>
        /// Play an animation clip on this model.
        /// </summary>
        /// <param name="aName"></param>
        /// <returns></returns>
        public AnimationPlayer PlayClip(string aName)
        {
            if (aName != "Take 001")
            {
                mClip = mAssetClips[aName].TheClip;
                mPlayer = new AnimationPlayer(this, mAssetClips[aName].TheClip);
                mPlayer.WindowSize = mGame.GraphicsDevice.Viewport.Height;
                if (this.mAsset == "Victoria"){
                    mPlayer.Speed = 5.0f;
                }
                Update(0);
                return mPlayer;
            }

            AnimationClips lClips = mModel.Tag as AnimationClips;
            if (lClips != null)
            {
                mClip = lClips.Clips[aName];
                mPlayer = new AnimationPlayer(this, lClips.Clips[aName]);
                mPlayer.WindowSize = mGame.GraphicsDevice.Viewport.Height;
            }


            return mPlayer;
        }

        public void AddAssetClip(string aName, string aAsset)
        {
            mAssetClips[aName] = new AssetClip(aName, aAsset);
        }

        public bool FinishedAnimation()
        {
            return mPlayer.Finished;
        }

        /// <summary>
        /// Returns the abso bone matrix for the associated model given the index of the bone
        /// </summary>
        /// <param name="aBoneIndex"></param>
        /// <returns></returns>
        public Matrix AbsoTransforms(string aBoneName)
        {
            return mAbsoTransforms[mModel.Bones[aBoneName].Index]; 
        }

        public void SetAbsoTransforms(string aBoneName, Matrix aTransform)
        {
            int lBoneID = mModel.Bones[aBoneName].Index;

            mAbsoTransforms[lBoneID] = aTransform;
        }

        public void SetBoneTransforms(string aBoneName, Matrix aTransform)
        {

            int lTemp = mModel.Bones[aBoneName].Index;
            mBoneTransforms[lTemp] = aTransform;
        }

        public Matrix GetBoneTransforms(string aBoneName)
        {
            return mBoneTransforms[mModel.Bones[aBoneName].Index];
        }

        public Matrix GetBindTransforms(string aBoneName)
        {
            return mBindTransforms[mModel.Bones[aBoneName].Index];
        }

        public void ComputeAbsoluteTransforms()
        {
            model.CopyBoneTransformsFrom(mBoneTransforms);
            model.CopyAbsoluteBoneTransformsTo(mAbsoTransforms);
        }
    }
}
